package com.example.springgobang.aip;

import com.example.springgobang.game.*;
import com.example.springgobang.model.User;
import com.example.springgobang.model.UserMapper;;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;

// 通过这个类来处理对战模块中的 WebSocket 请求
@Component
public class GameAPI extends TextWebSocketHandler {
    // 创建 RoomManager 对象, 用来获取玩家所在房间，还有进行释放房间的操作
    @Autowired
    private RoomManager roomManager;
    // 创建 ObjectMapper 对象, 用来处理 JSON 数据
    private ObjectMapper objectMapper = new ObjectMapper();
    // 创建 OnlineUserManager 用来管理玩家的状态信息
    @Autowired
    private OnlineUserManager onlineUserManager;
    // 创建 UserMapper 对象，用于更新对局结束后玩家的信息
    @Autowired
    private UserMapper userMapper;

    // 连接就绪后就会触发这个方法
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        GameReadyResponse response = new GameReadyResponse();
        // 1. 获取用户的身份信息. (从 HttpSession 里拿到当前用户的对象)
        User user = (User) session.getAttributes().get("user");
        if (user == null) {
            response.setOk(false);
            response.setReason("用户尚未登录!");
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
            return;
        }
        // 2. 判断当前用户是否已经进入房间. (用房间管理器进行查询)
        Room room = roomManager.getRoomByUserId(user.getUserId());
        if (room == null) {
            // 如果为 null ,当前没有找到对应的房间, 该玩家没有匹配成功.
            response.setOk(false);
            response.setReason("用户尚未匹配到对手!");
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
            return;
        }
        // 3. 判断当前是不是多开 (该用户是不是已经在其他地方进入游戏了)
        //    前面准备了一个 OnlineUserManager
        if (onlineUserManager.getFromGameHall(user.getUserId()) != null
                || onlineUserManager.getFromGameRoom((user.getUserId())) != null) {
            // 如果一个账号, 一边在游戏大厅, 一边在游戏房间, 也视为多开
            response.setOk(true);
            response.setReason("禁止多开游戏页面");
            response.setMessage("repeatConnection");
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
            return;
        }
        // 4. 设置当前玩家上线!
        onlineUserManager.enterGameRoom(user.getUserId(), session);
        // 5. 把两个玩家加入到游戏房间中.
        //    前面的创建房间/匹配过程, 是在 game_hall.html 页面中完成的.
        //    因此前面匹配到对手之后, 需要经过页面跳转, 来到 game_room.html 才算是正式进入游戏房间
        //    当前这个逻辑是在 game_room.html 页面加载的时候进行的.
        //    执行到当前逻辑, 说明玩家已经页面跳转成功了!!
        //    页面跳转, 要进行很多的操作, 很可能出现 "失败" 的情况
        synchronized (room) {
            if (room.getUser1() == null) {
                // 第一个玩家尚未加入房间.
                // 就把当前连上 WebSocket 的玩家作为 user1, 加入到房间中.
                room.setUser1(user);
                // 把先连入房间的玩家作为先手方.
                room.setWhiteUser(user.getUserId());
                System.out.println("玩家 " + user.getUsername() + " 已经准备就绪! 作为玩家1 ");
                return;
            }
            if (room.getUser2() == null) {
                // 如果进入这个逻辑, 说明玩家1 已经加入到房间, 现在要把当前玩家作为玩家2
                room.setUser2(user);
                System.out.println("玩家 " + user.getUsername() + " 已经准备就绪! 作为玩家2 ");
                // 当两个玩家都加入成功之后, 就要让服务器给这两个玩家都返回 WebSocket 的响应数据
                // 通知这两个玩家, 游戏双方都已经准备好了.
                // 通知玩家1
                noticeGameReady(room, room.getUser1(), room.getUser2());
                // 通知玩家2, 注意这里的传参顺序
                noticeGameReady(room, room.getUser2(), room.getUser1());
                return;
            }
        }
        // 6. 此处如果又有玩家尝试连接同一个房间, 就提示报错.
        //    这种情况理论上是不存在的, 为了让程序更加的健壮, 这里再做一个判断和提示.
        response.setOk(false);
        response.setReason("当前房间人数已满,您不能加入房间!");
        session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
    }

    // 用来给客户端返回 "准备就绪" 的响应
    private void noticeGameReady(Room room, User thisUser, User thatUser) throws IOException {
        // 构造 GameReadyResponse 作为返回的响应对象, 来返回响应
        GameReadyResponse response = new GameReadyResponse();
        response.setMessage("gameReady");
        response.setOk(true);
        response.setReason("");
        response.setRoomId(room.getRoomId());
        response.setThisUserId(thisUser.getUserId());
        response.setThatUserId(thatUser.getUserId());
        response.setWhiteUser(room.getWhiteUser());
        // 把当前的响应数据传回给对应的玩家
        // 获取当前玩家的连接信息
        WebSocketSession webSocketSession = onlineUserManager.getFromGameRoom(thisUser.getUserId());
        // 通过连接给当前玩家返回响应
        webSocketSession.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
    }

    // 客户端/服务器 给 服务器/客户端 发送信息通过这个方法就可以接收到信息
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        GameReadyResponse response = new GameReadyResponse();
        // 1. 先从 session 里拿到当前用户的身份信息
        User user = (User) session.getAttributes().get("user");
        if (user == null) {
            System.out.println("[handleTextMessage] 当前玩家尚未登录! ");
            response.setOk(false);
            response.setReason("用户尚未登录!");
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
            return;
        }
        // 2. 根据玩家 id 获取到房间对象
        Room room = roomManager.getRoomByUserId(user.getUserId());
        // 3. 通过 room 对象来处理这次具体的请求
        room.putChess(message.getPayload());
    }

    // 传输出现异常就会触发这个方法
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        User user = (User) session.getAttributes().get("user");
        if (user == null){
            // 此处简单处理, 在断开连接的时候不给客服端返回响应了.
            return;
        }
        WebSocketSession exitSession = onlineUserManager.getFromGameRoom(user.getUserId());
        if (exitSession == session) {
            // 加上这个判断, 目的是为了避免在多开的情况下, 第二个用户退出连接动作,导致第一个用户受到影响
            onlineUserManager.exitGameRoom(user.getUserId());
            // 通知对手获胜了
            noticeThatUserWin(user);
        }
        System.out.println("当前玩家: " + user.getUsername() + " 游戏房间连接异常");
    }

    // 如果客户端/服务器关闭连接就会执行这个方法
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        User user = (User) session.getAttributes().get("user");
        if (user == null){
            // 此处简单处理, 在断开连接的时候不给客服端返回响应了.
            return;
        }
        WebSocketSession exitSession = onlineUserManager.getFromGameRoom(user.getUserId());
        if (exitSession == session) {
            // 加上这个判断, 目的是为了避免在多开的情况下, 第二个用户退出连接动作,导致第一个用户受到影响
            onlineUserManager.exitGameRoom(user.getUserId());
            // 通知对手获胜了
            noticeThatUserWin(user);
        }
        System.out.println("当前玩家: " + user.getUsername() + " 已经离开游戏房间");
    }

    // 通知对手获胜
    private void noticeThatUserWin(User user) throws IOException {
        // 1. 根据当前玩家, 找到玩家所在的房间
        Room room = roomManager.getRoomByUserId(user.getUserId());
        if (room == null) {
            // 这个情况意味着房间已经释放了, 也就没有 "对手" 了
            System.out.println("当前房间已经释放, 无需通知对手!");
            return;
        }

        // 2. 根据房间找到对手
        User thatUser = (user == room.getUser1() ? room.getUser2() : room.getUser1());

        // 3. 找到对手的在线状态
        WebSocketSession webSocketSession = onlineUserManager.getFromGameRoom(thatUser.getUserId());
        if (webSocketSession == null) {
            // 说明对手也掉线了
            System.out.println("对手已经掉线, 无需通知");
            return;
        }
        // 4. 构造一个响应, 来通知对手: 它是获胜方
        GameResponse response = new GameResponse();
        response.setMessage("putChess");
        response.setUserId(thatUser.getUserId());
        response.setWinner(thatUser.getUserId());
        webSocketSession.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));

        // 5. 更新玩家的分数信息
        int winUserId = thatUser.getUserId();
        int loseUserId = user.getUserId();
        userMapper.userWin(winUserId);
        userMapper.userLose(loseUserId);

        // 6. 释放房间对象
        roomManager.remove(room.getRoomId(), room.getUser1().getUserId(), room.getUser2().getUserId());
    }
}
